﻿using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.Caching;
using System.Threading;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace Monitor.Filters
{
    /// <summary>
    /// фильтр кэширования
    /// </summary>
    public class WebApiOutputCacheAttribute : ActionFilterAttribute
    {
        // cache length in seconds
        private int _timespan;
        // client cache length in seconds
        private int _clientTimeSpan;
        // cache for anonymous users only?
        private bool _allowForAll;
        // cache key
        private string _cachekey;
        // cache repository
        private static readonly ObjectCache WebApiCache = MemoryCache.Default;

        /// <summary>
        /// Строчка объявления кэша
        /// </summary>
        /// <param name="timespan">продолжительность кэширования на стороне сервера, с</param>
        /// <param name="clientTimeSpan">продолжительность кэширования на стороне клиента, с</param>
        /// <param name="allowForAll">разрешить использовать кэш всем пользователям</param>
        public WebApiOutputCacheAttribute(int timespan=300, int clientTimeSpan=5, bool allowForAll=false)
        {
            _timespan = timespan;
            _clientTimeSpan = clientTimeSpan;
            _allowForAll = allowForAll;
        }

        /// <summary>
        /// Хеш-функция
        /// </summary>
        /// <param name="actionContext">контекст запроса</param>
        /// <returns>строчка кэш-ключа</returns>
        private string CacheKeyCreate(HttpActionContext actionContext)
        {
            return (_allowForAll)
                        //ключ для всех зарегистрированных
                       ?  actionContext.Request.RequestUri.AbsoluteUri + ":" +
                         actionContext.Request.Headers.Accept.FirstOrDefault().ToString()
                         //ключ для конкретного пользователя
                       : Thread.CurrentPrincipal.Identity.Name + ":" + actionContext.Request.RequestUri.AbsoluteUri + ":" +
                         actionContext.Request.Headers.Accept.FirstOrDefault().ToString();
        }
        /// <summary>
        /// Проверка на разрешение кэширования
        /// </summary>
        /// <param name="ac">тело запроса</param>
        /// <returns>истина, если кэширование разрешено</returns>
        private bool _isCacheable(HttpActionContext ac)
        {
            if (_timespan > 0 && _clientTimeSpan > 0)
            {
                if (ac.Request.Method == HttpMethod.Get) 
                    return true;
            }
            else
            {
                throw new InvalidOperationException("Wrong Arguments");
            }
            return false;
        }
        /// <summary>
        /// Назначение кэширование пользователю
        /// </summary>
        /// <returns>объект управление кэшем</returns>
        private CacheControlHeaderValue setClientCache()
        {
            CacheControlHeaderValue cachecontrol = new CacheControlHeaderValue();
            cachecontrol.MaxAge = TimeSpan.FromSeconds(_clientTimeSpan);
            cachecontrol.MustRevalidate = true;
            return cachecontrol;
        }
        /// <summary>
        /// Переопределенная функция выдачи результата
        /// </summary>
        /// <param name="actionExecutedContext">тело ответа</param>
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            if (!(WebApiCache.Contains(_cachekey)))
            {
                var body = actionExecutedContext.Response.Content.ReadAsStringAsync().Result;
                WebApiCache.Add(_cachekey, body, DateTime.Now.AddSeconds(_timespan));
                WebApiCache.Add(_cachekey + ":response-ct", actionExecutedContext.Response.Content.Headers.ContentType, DateTime.Now.AddSeconds(_timespan));
            }
            if (_isCacheable(actionExecutedContext.ActionContext))
                actionExecutedContext.ActionContext.Response.Headers.CacheControl = setClientCache();
        }
        /// <summary>
        /// Переопределенная функция получения запроса
        /// </summary>
        /// <param name="ac">тело запроса</param>
        public override void OnActionExecuting(HttpActionContext ac)
        {
            if (ac != null)
            {
                if (_isCacheable(ac) && Thread.CurrentPrincipal.Identity.IsAuthenticated)
                {
                    _cachekey = CacheKeyCreate(ac);
                    
                    if (WebApiCache.Contains(_cachekey))
                    {
                        var val = (string)WebApiCache.Get(_cachekey);
                        if (val != null)
                        {
                            ac.Response = ac.Request.CreateResponse();
                            ac.Response.Content = new StringContent(val);
                            var contenttype = (MediaTypeHeaderValue)WebApiCache.Get(_cachekey + ":response-ct");
                            if (contenttype == null)
                                contenttype = new MediaTypeHeaderValue(_cachekey.Split(':')[3]);
                            ac.Response.Content.Headers.ContentType = contenttype;
                            ac.Response.Headers.CacheControl = setClientCache();
                            return;
                        }
                    }
                }
            }
            else
            {
                throw new ArgumentNullException("actionContext");
            }
        }

    }
}